 /*******************************************************************************
  * Copyright (c) 2005, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.internal.menus;

 import java.util.ArrayList ;
 import java.util.Collection ;
 import java.util.HashMap ;
 import java.util.HashSet ;
 import java.util.Iterator ;
 import java.util.Map ;
 import java.util.Set ;

 import org.eclipse.jface.window.Window;
 import org.eclipse.ui.internal.services.ExpressionAuthority;

 /**
  * <p>
  * A central authority for deciding visibility of menu elements. This authority
  * listens to a variety of incoming sources, and updates the underlying menu
  * manager if changes occur. It only updates those menu elements that are
  * showing, and listens to the menu manager to determine which menu elements are
  * currently visible.
  * </p>
  * <p>
  * This class is not intended for use outside of the
  * <code>org.eclipse.ui.workbench</code> plug-in.
  * </p>
  *
  * @since 3.2
  */
 final class MenuAuthority extends ExpressionAuthority {

     /**
      * This is a map of menu element contributions (<code>Collection</code>
      * of <code>IMenuContribution</code>) sorted by identifier (<code>MenuElement</code>).
      * If there is only one contribution for a menu element, then the
      * <code>Collection</code> is replaced by a <code>IMenuContribution</code>.
      * If there is no contribution, the entry should be removed entirely.
      */
     private final Map menuContributionsByElement = new HashMap ();

     /**
      * A bucket sort of the contributed menu elements based on source priority.
      * This only includes the items that are currently showing within the
      * workbench. Each contribution will appear only once per set, but may
      * appear in multiple sets. If no contributions are defined for a particular
      * priority level, then the array at that index will only contain
      * <code>null</code>.
      */
     private final Set [] showingContributionsBySourcePriority = new Set [33];

     /**
      * The window for which this authority applies. This is used for determining
      * visibility of particular menu elements.
      */
     private final Window window;

     /**
      * Constructs a new instance of <code>MenuAuthority</code>.
      *
      * @param window
      * The window to use when calling
      * {@link MenuElement#setVisible(Window, boolean)}; may be
      * <code>null</code>.
      */
     MenuAuthority(final Window window) {
         this.window = window;
     }

     /**
      * Contributes a menu element to the workbench. This will add it to a master
      * list.
      *
      * @param contribution
      * The contribution; must not be <code>null</code>.
      */
     final void contributeMenu(final IMenuContribution contribution) {
         // First we update the menuContributionsByElement map.
 final MenuElement element = contribution.getMenuElement();
         final Object value = menuContributionsByElement.get(element);
         if (value instanceof Collection ) {
             final Collection menuContributions = (Collection ) value;
             if (!menuContributions.contains(contribution)) {
                 menuContributions.add(contribution);
             }
         } else if (value instanceof IMenuContribution) {
             if (value != contribution) {
                 final Collection menuContributions = new ArrayList (2);
                 menuContributions.add(value);
                 menuContributions.add(contribution);
                 menuContributionsByElement.put(element, menuContributions);
             }
         } else {
             menuContributionsByElement.put(element, contribution);
         }

         // Next we update the source priority bucket sort of activations.
 if (contribution.getMenuElement().isShowing(window)) {
             final int sourcePriority = contribution.getSourcePriority();
             for (int i = 1; i <= 32; i++) {
                 if ((sourcePriority & (1 << i)) != 0) {
                     Set contributions = showingContributionsBySourcePriority[i];
                     if (contributions == null) {
                         contributions = new HashSet (1);
                         showingContributionsBySourcePriority[i] = contributions;
                     }
                     contributions.add(contribution);
                 }
             }
         }
     }

     /**
      * Removes a contribution from the workbench. This will remove it from the
      * master list, and update as appropriate.
      *
      * @param contribution
      * The contribution; must not be <code>null</code>.
      */
     final void removeContribution(final IMenuContribution contribution) {
         // First we update the menuContributionByElement map.
 final MenuElement element = contribution.getMenuElement();
         final Object value = menuContributionsByElement.get(element);
         if (value instanceof Collection ) {
             final Collection menuContributions = (Collection ) value;
             if (menuContributions.contains(contribution)) {
                 menuContributions.remove(contribution);
                 if (menuContributions.isEmpty()) {
                     menuContributionsByElement.remove(element);

                 } else if (menuContributions.size() == 1) {
                     final IMenuContribution remainingContribution = (IMenuContribution) menuContributions
                             .iterator().next();
                     menuContributionsByElement.put(element,
                             remainingContribution);

                 }
             }
         } else if (value instanceof IMenuContribution) {
             if (value == contribution) {
                 menuContributionsByElement.remove(element);
             }
         }

         // Next we update the source priority bucket sort of activations.
 if (element.isShowing(window)) {
             final int sourcePriority = contribution.getSourcePriority();
             for (int i = 1; i <= 32; i++) {
                 if ((sourcePriority & (1 << i)) != 0) {
                     final Set contributions = showingContributionsBySourcePriority[i];
                     if (contributions == null) {
                         continue;
                     }
                     contributions.remove(contribution);
                     if (contributions.isEmpty()) {
                         showingContributionsBySourcePriority[i] = null;
                     }
                 }
             }
         }
     }

     /**
      * Carries out the actual source change notification. It assumed that by the
      * time this method is called, <code>getEvaluationContext()</code> is
      * up-to-date with the current state of the application.
      *
      * @param sourcePriority
      * A bit mask of all the source priorities that have changed.
      */
     protected final void sourceChanged(final int sourcePriority) {
         /*
          * In this first phase, we cycle through all of the contributions that
          * could have potentially changed. Each such contribution is added to a
          * set for future processing. We add it to a set so that we avoid
          * handling any individual contribution more than once.
          */
         final Set contributionsToRecompute = new HashSet ();
         for (int i = 1; i <= 32; i++) {
             if ((sourcePriority & (1 << i)) != 0) {
                 final Collection contributions = showingContributionsBySourcePriority[i];
                 if (contributions != null) {
                     final Iterator contributionItr = contributions.iterator();
                     while (contributionItr.hasNext()) {
                         contributionsToRecompute.add(contributionItr.next());
                     }
                 }
             }
         }

         /*
          * For every contribution, we recompute its state, and check whether it
          * has changed. If it has changed, then we take note of the menu element
          * so we can update the menu element later.
          */
         final Iterator contributionItr = contributionsToRecompute.iterator();
         while (contributionItr.hasNext()) {
             final IMenuContribution contribution = (IMenuContribution) contributionItr
                     .next();
             final boolean currentlyVisible = evaluate(contribution);
             contribution.clearResult();
             final boolean newVisible = evaluate(contribution);
             if (newVisible != currentlyVisible) {
                 contribution.getMenuElement().setVisible(window, newVisible);
             }
         }
     }
 }

